package games.puzzle8;

import gps.ProductionSystem;
import gps.methods.astar.AStarMethod;
import gps.methods.dfs_inform.DfsInformMethod;
import gps.methods.ida_star.IdaStarMethod;
import gps.methods.uniform_cost.UniformCostMethod;
import gps_api.Problem;
import gps_api.Rule;
import gps_api.State;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class Puzzle8Problem implements Problem {
	static final int HEU_NOTHING=0;
	static final int HEU_RELAX=1;
	static final int HEU_MANHATTAN=2;

	private Puzzle8State goal;
	private int []initState;
	private int heu;

	public Puzzle8Problem(int []initState) throws Exception {
		
		goal= getGoalState();
		this.initState= initState;
		if(!isValidPermutation()){
			throw new Exception("This Puzzle configuration is unsolvable");
		}
	}
	
	public Puzzle8Problem () throws Exception{
		initState= new int[9];
		goal= getGoalState();
		
		Properties configFile= new Properties();
		
		configFile.load(this.getClass().getClassLoader().getResourceAsStream("games\\puzzle8\\puzzle8.conf"));
		setHeuristic(configFile.getProperty("heuristic"));
		setInitState(configFile.getProperty("initState"));
		
		if(!isValidPermutation()){
			throw new Exception("This Puzzle configuration is unsolvable");
		}
		
	}
	
	private void setHeuristic(String heuristic){
		if(heuristic.equals("relax")){
			setHeu(HEU_RELAX);
		}else if (heuristic.equals("nothing")){
			setHeu(HEU_NOTHING);
		}else{
			setHeu(HEU_MANHATTAN);
		}
	}
	
	private void setInitState(String init){
		String [] numbers=init.split(",");
		int i=0;
		
		for (String num : numbers) {
			initState[i++]= Integer.valueOf(num);
		}
		
	}

	@Override
	public Double getHeuristic(State state) {
		Double ans=0.0;
		switch (heu) {
		case HEU_RELAX:
			ans=getRelaxPuzzle(state);
			break;
		case HEU_MANHATTAN:
			ans=getManhattan(state);
			break;
		}

		return ans;
	}

	private Double getManhattan(State state){
		Double heuristic=0.0;
		Puzzle8State state1=(Puzzle8State)state;
		int [][]vec={{2,2}, {0,0}, {0,1}, {0,2}, {1,0}, {1,1}, {1,2}, {2,0}, {2,1}};

		for(int i=0; i<Puzzle8State.SIZE_TABLE;i++ ){
			for(int j=0; j< Puzzle8State.SIZE_TABLE; j++){
				if(state1.table[i][j]!=0){
					heuristic+= Math.abs((vec[state1.table[i][j]][0]-i))+Math.abs((vec[state1.table[i][j]][1]-j));
				}
			}
		}
		return heuristic;
	}

	private Double getRelaxPuzzle(State state){
		Double heuristic=0.0;
		Puzzle8State state1=(Puzzle8State)state;

		for(int i=0; i<Puzzle8State.SIZE_TABLE;i++ ){
			for(int j=0; j< Puzzle8State.SIZE_TABLE; j++){
				if(goal.table[i][j]!= state1.table[i][j]){
					heuristic+=1;
				}
			}
		}

		return heuristic;
	}

	@Override
	public State getInitState() {
		Puzzle8State state= new Puzzle8State();
		int i=0, pos_x=0, pos_y = 0;

		for(int y=0; y<Puzzle8State.SIZE_TABLE;y++){
			for(int x=0; x<Puzzle8State.SIZE_TABLE;x++){
				state.table[y][x]=initState[i];
				if(state.table[y][x]==0){
					pos_x=x;
					pos_y=y;
				}
				i++;
			}
		}

		state.setEmptyPos(pos_y, pos_x);
		
		return state;
	}

	public Puzzle8State getGoalState(){
		Puzzle8State state= new Puzzle8State();
		int []vec= {1,2,3,4,5,6,7,8,0};
		int i=0;

		for(int x=0; x<Puzzle8State.SIZE_TABLE;x++){
			for(int y=0; y<Puzzle8State.SIZE_TABLE;y++){
				state.table[x][y]=vec[i];
				i++;
			}
		}
		return state;
	}

	@Override
	public List<Rule> getRules() {
		List<Rule> rules= new ArrayList<Rule>();

		rules.add(new Puzzle8Rule(Puzzle8Rule.MOV_UP));
		rules.add(new Puzzle8Rule(Puzzle8Rule.MOV_RIGHT));
		rules.add(new Puzzle8Rule(Puzzle8Rule.MOV_LEFT));
		rules.add(new Puzzle8Rule(Puzzle8Rule.MOV_DOWN));

		return rules;
	}

	@Override
	public boolean isGoal(State state) {
		return goal.equals(state);
	}


	public void setHeu(int heu) {
		this.heu = heu;
	}

	/**
	 * If the number of inversions is even then
	 * the puzzle is solvable. If the number of
	 * inversions is odd then the puzzle is unsolvable.
	 *
	 * 2 4 3
	 * 1 0 6
	 * 7 5 8
	 * We have the following inversions:
	 * 2 comes before 1
	 * 4 comes before 3 and 1
	 * 3 comes before 1
	 * 6 comes before 5
	 * 7 comes before 5
	 *
	 * for a total of 6 inversions. This puzzle is solvable.
	 * Note that we don't need to consider the blank (0).
	 *
	 * @return
	 */
	private boolean isValidPermutation(){
		boolean ans= true;
		int count=0;
		int size=Puzzle8State.SIZE_TABLE*Puzzle8State.SIZE_TABLE;

		for(int i=0; i<size;i++){
			for(int j=i; j<size; j++){
				if(initState[i]!=0 && initState[j]!=0){
					if(initState[i]>initState[j]){
						count++;
					}
				}
			}
		}

		if(count % 2 !=0){
			ans=false;
		}

		return ans;
	}

	public static void main(String[] args) {
		int []puzzle={1,0,8,7,5,6,2,4,3};
		Puzzle8Problem problem=null;

		try {
			problem = new Puzzle8Problem();

			UniformCostMethod method= new UniformCostMethod();
			DfsInformMethod method2= new DfsInformMethod(problem);
			AStarMethod method3= new AStarMethod(problem);
			IdaStarMethod method4= new IdaStarMethod(problem);



			//problem.setHeu(Puzzle8Problem.HEU_RELAX);
			ProductionSystem ps= new ProductionSystem(problem,method3);
			//ps.runSystem();

			//problem.setHeu(Puzzle8Problem.HEU_RELAX);
			ps.runSystem();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}
